/* SPDX-License-Identifier: GPL-2.0 */
/******************************************************************************
 *
 * Copyright(c) 2016 - 2017 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#define _RTL8821CS_RECV_C_

#include <drv_types.h>		/* PADAPTER and etc. */
#include <hal_data.h>		/* HAL_DATA_TYPE */
#include "../../hal_halmac.h"	/* BIT_ACRC32_8821C and etc. */
#include "../rtl8821c.h"	/* rtl8821c_rxdesc2attribute(), rtl8821c_c2h_handler_no_io() */
#include "rtl8821cs_recv.h"	/* MAX_RECVBUF_SZ */

#if 0
/*
 * Return:
 *	Pointer of _pkt, otherwise NULL.
 */
static _pkt *alloc_recvbuf_skb(struct recv_buf *recvbuf, u32 size)
{
	_pkt *skb;
	u32 alignsz = RECVBUFF_ALIGN_SZ;
#ifdef PLATFORM_LINUX
	SIZE_PTR tmpaddr = 0;
	SIZE_PTR alignment = 0;
#endif /* PLATFORM_LINUX */


	size += alignsz;
	skb = rtw_skb_alloc(size);
	if (!skb) {
		RTW_INFO("%s: <WARN> alloc_skb fail! size=%d\n", __FUNCTION__, size);
		return NULL;
	}

#ifdef PLATFORM_LINUX
	skb->dev = recvbuf->adapter->pnetdev;

	tmpaddr = (SIZE_PTR)skb->data;
	alignment = tmpaddr & (alignsz - 1);
	skb_reserve(skb, alignsz - alignment);
#endif /* PLATFORM_LINUX */

	recvbuf->pskb = skb;

	return skb;
}

/*
 * Description:
 *	Allocate skb for recv_buf, the size is MAX_RECVBUF_SZ_8821C (24KB)
 *
 * Parameters:
 *	recvbuf	pointer of struct recv_buf
 *	size	skb size, only valid when NOT define CONFIG_SDIO_RX_COPY.
 *		If CONFIG_SDIO_RX_COPY, size always be MAX_RECVBUF_SZ_8821C.
 *
 * Return:
 *	Pointer of _pkt, otherwise NULL.
 */
_pkt *rtl8821cs_alloc_recvbuf_skb(struct recv_buf *recvbuf, u32 size)
{
	_pkt *skb;


	skb = recvbuf->pskb;
#ifdef CONFIG_SDIO_RX_COPY
	if (skb) {
		skb_reset_tail_pointer(skb);
		skb->len = 0;
		return skb;
	}

	RTW_INFO("%s: <WARN> skb not exist in recv_buf!\n", __FUNCTION__);
	size = MAX_RECVBUF_SZ_8821C;
#else /* !CONFIG_SDIO_RX_COPY */
	if (skb) {
		RTW_INFO("%s: <WARN> skb already exist in recv_buf!\n", __FUNCTION__);
		rtl8821cs_free_recvbuf_skb(recvbuf);
	}
#endif /* !CONFIG_SDIO_RX_COPY */

	skb = alloc_recvbuf_skb(recvbuf, size);
	if (!skb)
		return NULL;

	return skb;
}

static void free_recvbuf_skb(struct recv_buf *recvbuf)
{
	_pkt *skb;


	skb = recvbuf->pskb;
	if (!skb)
		return;
	recvbuf->pskb = NULL;
	rtw_skb_free(skb);
}

void rtl8821cs_free_recvbuf_skb(struct recv_buf *recvbuf)
{
#ifndef CONFIG_SDIO_RX_COPY
	free_recvbuf_skb(recvbuf);
#endif /* !CONFIG_SDIO_RX_COPY */
}

/*
 * Return:
 *	_SUCCESS	Allocate resource OK.
 *	_FAIL		Fail to allocate resource.
 */
static inline s32 os_recvbuf_resource_alloc(PADAPTER adapter, struct recv_buf *recvbuf)
{
	s32 ret = _SUCCESS;

#ifdef CONFIG_SDIO_RX_COPY
	alloc_recvbuf_skb(recvbuf, MAX_RECVBUF_SZ_8821C);
#endif /* CONFIG_SDIO_RX_COPY */

	return ret;
}

static inline void os_recvbuf_resource_free(PADAPTER adapter, struct recv_buf *recvbuf)
{
#ifdef CONFIG_SDIO_RX_COPY
	free_recvbuf_skb(recvbuf);
#endif /* CONFIG_SDIO_RX_COPY */
}

static union recv_frame *copy_recvframe(union recv_frame *recvframe, PADAPTER adapter)
{
	PHAL_DATA_TYPE pHalData;
	struct recv_priv *precvpriv;
	_queue *pfree_recv_queue;
	struct rx_pkt_attrib *attrib = NULL;
	union recv_frame *copyframe = NULL;
	_pkt *copypkt = NULL;


	pHalData = GET_HAL_DATA(adapter);
	precvpriv = &adapter->recvpriv;
	pfree_recv_queue = &precvpriv->free_recv_queue;
	attrib = &recvframe->u.hdr.attrib;

	copyframe = rtw_alloc_recvframe(pfree_recv_queue);
	if (!copyframe) {
		RTW_INFO(FUNC_ADPT_FMT ": Alloc recvframe FAIL!\n",
			 FUNC_ADPT_ARG(adapter));
		return NULL;
	}
	copyframe->u.hdr.adapter = adapter;
	_rtw_memcpy(&copyframe->u.hdr.attrib, attrib, sizeof(struct rx_pkt_attrib));
#if 0
	/*
	 * driver need to set skb len for skb_copy().
	 * If skb->len is zero, skb_copy() will not copy data from original skb.
	 */
	skb_put(recvframe->u.hdr.pkt, attrib->pkt_len);
#else
	RTW_INFO(FUNC_ADPT_FMT ": skb len=%d!\n",
		 FUNC_ADPT_ARG(adapter), recvframe->u.hdr.pkt->len);
#endif

	copypkt = rtw_skb_copy(recvframe->u.hdr.pkt);
	if (!copypkt) {
		if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
			RTW_INFO(FUNC_ADPT_FMT ": <ERR> rtw_skb_copy fail for first fragment!\n",
				 FUNC_ADPT_ARG(adapter));
			rtw_free_recvframe(recvframe, &precvpriv->free_recv_queue);
			return NULL;
		}

		copypkt = rtw_skb_clone(recvframe->u.hdr.pkt);
		if (!copypkt) {
			RTW_INFO(FUNC_ADPT_FMT ": <ERR> rtw_skb_clone fail, drop frame!\n",
				 FUNC_ADPT_ARG(adapter));
			rtw_free_recvframe(recvframe, &precvpriv->free_recv_queue);
			return NULL;
		}
	}
	copypkt->dev = adapter->pnetdev;

	copyframe->u.hdr.pkt = copypkt;
	copyframe->u.hdr.len = copypkt->len;
	copyframe->u.hdr.rx_head = copypkt->head;
	copyframe->u.hdr.rx_data = copypkt->data;
	copyframe->u.hdr.rx_tail = skb_tail_pointer(copypkt);
	copyframe->u.hdr.rx_end = skb_end_pointer(copypkt);

	return copyframe;
}

/*
 * Return:
 *	_SUCCESS	OK to send packet
 *	_FAIL		FAIL to send packet
 */
static s32 recv_entry(union recv_frame *recvframe, u8 *phy_status)
{
	s32 ret = _SUCCESS;
	PADAPTER adapter;
	struct rx_pkt_attrib *attrib = NULL;
#ifdef CONFIG_CONCURRENT_MODE
	struct dvobj_priv *d;
	u8 *addr1, *macaddr;
	u8 mcast, i;
	union recv_frame *copyframe = NULL;
#endif /* CONFIG_CONCURRENT_MODE */


	attrib = &recvframe->u.hdr.attrib;

#ifdef CONFIG_CONCURRENT_MODE
	d = adapter_to_dvobj(recvframe->u.hdr.adapter);
	addr1 = GetAddr1Ptr(recvframe->u.hdr.rx_data);
	mcast = IS_MCAST(addr1);
	if (_TRUE == mcast) {
		/* BC/MC packets */
		for (i = 1; i < d->iface_nums; i++) {
			adapter = d->adapters[i];

			if (rtw_if_up(adapter) == _FALSE)
				continue;

			copyframe = copy_recvframe(recvframe, adapter);
			if (!copyframe)
				break;

			if (attrib->physt)
				rx_query_phy_status(copyframe, phy_status);

			ret = rtw_recv_entry(copyframe);
		}
	} else {
		/* unicast packets */
		for (i = 0; i < d->iface_nums; i++) {
			adapter = d->adapters[i];

			if (rtw_if_up(adapter) == _FALSE)
				continue;

			macaddr = adapter_mac_addr(adapter);
			if (_rtw_memcmp(addr1, macaddr, ETH_ALEN) == _FALSE)
				continue;

			/* change to target interface */
			recvframe->u.hdr.adapter = adapter;
			recvframe->u.hdr.pkt->dev = adapter->pnetdev;
			break;
		}
	}
#endif /* CONFIG_CONCURRENT_MODE */

	if (attrib->physt)
		rx_query_phy_status(recvframe, phy_status);

	ret = rtw_recv_entry(recvframe);

	return ret;
}

/*
 * Return:
 *	_TRUE	Finish preparing recv_frame
 *	_FALSE	Something fail to prepare recv_frame
 */
static _pkt *prepare_recvframe_pkt(struct recv_buf *recvbuf, union recv_frame *recvframe)
{
	u32 desc_size;
	_pkt *pkt = NULL;
	struct rx_pkt_attrib *attrib;
	u32 skb_len;
	u8 *data;
#ifdef CONFIG_SDIO_RX_COPY
	u32 shift_sz, alloc_sz;
#endif /* CONFIG_SDIO_RX_COPY */

	rtw_halmac_get_rx_desc_size(adapter_to_dvobj(recvbuf->adapter), &desc_size);

	pkt = recvframe->u.hdr.pkt;
	if (pkt) {
		RTW_INFO("%s: <WARN> recvframe pkt already exist!\n", __FUNCTION__);
		return pkt;
	}

	attrib = &recvframe->u.hdr.attrib;
	skb_len = attrib->pkt_len;
	if (rtl8821c_rx_fcs_appended(recvbuf->adapter))
		skb_len -= IEEE80211_FCS_LEN;
	data = recvbuf->pdata + desc_size + attrib->drvinfo_sz;
#if 0
	data += attrib->shift_sz;
#endif

#ifdef CONFIG_SDIO_RX_COPY
	/* For 8 bytes IP header alignment. */
	if (attrib->qos)
		/* Qos data, wireless lan header length is 26 */
		shift_sz = 6;
	else
		shift_sz = 0;

	/*
	 * For first fragment packet, driver need allocate
	 * (1536 + drvinfo_sz + RXDESC_SIZE) to defrag packet.
	 * In 8821C, drvinfo_sz = 32, RXDESC_SIZE = 24, 1536 + 32 + 24 = 1592.
	 * And need 8 is for skb->data 8 bytes alignment.
	 * Round (1536 + 24 + 32 + shift_sz + 8) to 128 bytes alignment,
	 * and finally get 1664.
	 */
	if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
		if (skb_len <= 1650)
			alloc_sz = 1664;
		else
			alloc_sz = skb_len + 14;
	} else {
		alloc_sz = skb_len;
		/*
		 * 6 is for IP header 8 bytes alignment in QoS packet case.
		 * 8 is for skb->data 4 bytes alignment.
		 */
		alloc_sz += 14;
	}

	pkt = rtw_skb_alloc(alloc_sz);
	if (pkt) {
		pkt->dev = recvframe->u.hdr.adapter->pnetdev;
		/* force pkt->data at 8-byte alignment address */
		skb_reserve(pkt, 8 - ((SIZE_PTR)pkt->data & 7));
		/* force ip_hdr at 8-byte alignment address according to shift_sz. */
		skb_reserve(pkt, shift_sz);
		_rtw_memcpy(skb_put(pkt, skb_len), data, skb_len);
	} else if ((attrib->mfrag == 1) && (attrib->frag_num == 0)) {
		RTW_INFO("%s: <ERR> alloc_skb fail for first fragement\n", __FUNCTION__);
		return NULL;
	}
#endif /* CONFIG_SDIO_RX_COPY */

	if (!pkt) {
		pkt = rtw_skb_clone(recvbuf->pskb);
		if (!pkt) {
			RTW_INFO("%s: <ERR> rtw_skb_clone fail\n", __FUNCTION__);
			return NULL;
		}
		pkt->data = data;
		skb_set_tail_pointer(pkt, skb_len);
		pkt->len = skb_len;
	}

	recvframe->u.hdr.pkt = pkt;
	recvframe->u.hdr.len = pkt->len;
	recvframe->u.hdr.rx_head = pkt->head;
	recvframe->u.hdr.rx_data = pkt->data;
	recvframe->u.hdr.rx_tail = skb_tail_pointer(pkt);
	recvframe->u.hdr.rx_end = skb_end_pointer(pkt);

	return pkt;
}

/*
 * process Normal Rx packets
 * Return:
 *	_TRUE	Finish processing recv_buf
 *	_FALSE	Something fail to process recv_buf
 */
static u8 recvbuf_handler(struct recv_buf *recvbuf)
{
	PADAPTER p;
	struct recv_priv *recvpriv;
	union recv_frame *recvframe;
	struct rx_pkt_attrib *attrib;
	_pkt *pkt;
	u32 desc_size;
	u32 rx_report_sz, pkt_offset;
	u8 *ptr;
	u8 ret = _TRUE;


	p = recvbuf->adapter;
	recvpriv = &p->recvpriv;
	ptr = recvbuf->pdata;
	rtw_halmac_get_rx_desc_size(adapter_to_dvobj(p), &desc_size);

	while (ptr < recvbuf->ptail) {
		recvframe = rtw_alloc_recvframe(&recvpriv->free_recv_queue);
		if (!recvframe) {
			RTW_INFO("%s: <WARN> no enough recv frame!\n", __FUNCTION__);
			ret = _FALSE;
			break;
		}

		/* rx desc parsing */
		attrib = &recvframe->u.hdr.attrib;
		rtl8821c_rxdesc2attribute(attrib, ptr);

		rx_report_sz = desc_size + attrib->drvinfo_sz;
		pkt_offset = rx_report_sz + attrib->shift_sz + attrib->pkt_len;

		if ((ptr + pkt_offset) > recvbuf->ptail) {
			RTW_INFO("%s: <WARN> next pkt len(%p,%d) exceed ptail(%p)!\n",
				 __FUNCTION__, ptr, pkt_offset, recvbuf->ptail);
			rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
			break;
		}

		/* fix Hardware RX data error, drop whole recv_buffer */
		if (!rtw_hal_rcr_check(p, BIT_ACRC32_8821C)
		    && attrib->crc_err) {
			RTW_INFO("%s: <WARN> Received unexpected CRC error packet!!\n", __FUNCTION__);
			rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
			break;
		}

		if ((attrib->crc_err) || (attrib->icv_err)) {
#ifdef CONFIG_MP_INCLUDED
			if (p->registrypriv.mp_mode == 1) {
				if (check_fwstate(&p->mlmepriv, WIFI_MP_STATE) == _TRUE) {
					if (attrib->crc_err == 1)
						p->mppriv.rx_crcerrpktcount++;
				}
			} else
#endif /* CONFIG_MP_INCLUDED */
			{
				RTW_INFO("%s: crc_err=%d icv_err=%d, skip!\n",
					__FUNCTION__, attrib->crc_err, attrib->icv_err);
			}
			rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
		} else {
			pkt = prepare_recvframe_pkt(recvbuf, recvframe);
			if (!pkt) {
				rtw_free_recvframe(recvframe, &recvpriv->free_recv_queue);
				ret = _FALSE;
				break;
			}

			/* move to start of PHY_STATUS */
			ptr += desc_size;
			if (rtl8821c_rx_ba_ssn_appended(p))
				ptr += RTW_HALMAC_BA_SSN_RPT_SIZE;

			recv_entry(recvframe, ptr);
		}

		pkt_offset = _RND8(pkt_offset);
		recvbuf->pdata += pkt_offset;
		ptr = recvbuf->pdata;
	}

	return ret;
}

static void rtl8821c_recv_tasklet(void *priv)
{
	PADAPTER adapter;
	struct recv_priv *recvpriv;
	struct recv_buf *recvbuf;
	u8 ret = _TRUE;


	adapter = (PADAPTER)priv;
	recvpriv = &adapter->recvpriv;

	do {
		recvbuf = rtw_dequeue_recvbuf(&recvpriv->recv_buf_pending_queue);
		if (NULL == recvbuf)
			break;


		if (GET_RX_DESC_C2H_8821C(recvbuf->pdata)) {
			if (GET_RX_DESC_DRV_INFO_SIZE_8821C(recvbuf->pdata)) {
				RTW_INFO("%s [WARN] DRV_INFO_SIZE != 0\n", __func__);
				rtw_warn_on(1);
			}

			c2h_pre_handler_rtl8821c(adapter, recvbuf->pdata, recvbuf->len);
		} else
			ret = recvbuf_handler(recvbuf);

		if (_FALSE == ret) {
			rtw_enqueue_recvbuf_to_head(recvbuf, &recvpriv->recv_buf_pending_queue);
			rtw_msleep_os(5);
			start_rx_handle(adapter);
			break;
		}

		/* free recv_buf */
		rtl8821cs_free_recvbuf_skb(recvbuf);
		rtw_enqueue_recvbuf(recvbuf, &recvpriv->free_recv_buf_queue);
	} while (1);
}
#endif

#ifdef CONFIG_SDIO_RX_COPY
s32 rtl8821cs_recv_hdl(_adapter *padapter)
{
	PHAL_DATA_TYPE		pHalData;
	struct recv_priv		*precvpriv;
	struct recv_buf		*precvbuf;
	union recv_frame		*precvframe;
	struct recv_frame_hdr	*phdr;
	struct rx_pkt_attrib	*pattrib;
	u8		*ptr;
	u32		desc_size;
	u32		pkt_offset;
	s32		transfer_len;
	u8		*pphy_status = NULL;
	u8		rx_report_sz = 0;

	pHalData = GET_HAL_DATA(padapter);
	precvpriv = &padapter->recvpriv;

	rtw_halmac_get_rx_desc_size(adapter_to_dvobj(padapter), &desc_size);

	do {
		if (RTW_CANNOT_RUN(padapter)) {
			RTW_INFO("%s => bDriverStopped or bSurpriseRemoved\n", __func__);
			break;
		}

		precvbuf = rtw_dequeue_recvbuf(&precvpriv->recv_buf_pending_queue);
		if (NULL == precvbuf)
			break;

		transfer_len = (s32)precvbuf->len;
		ptr = precvbuf->pdata;

		do {
			precvframe = rtw_alloc_recvframe(&precvpriv->free_recv_queue);
			if (precvframe == NULL) {
				rtw_enqueue_recvbuf_to_head(precvbuf, &precvpriv->recv_buf_pending_queue);
				return RTW_RFRAME_UNAVAIL;
			}

			/*rx desc parsing*/
			pattrib = &precvframe->u.hdr.attrib;
			rtl8821c_rxdesc2attribute(pattrib, ptr);

			/* fix Hardware RX data error, drop whole recv_buffer*/
			if (!rtw_hal_rcr_check(padapter, BIT_ACRC32_8821C) && pattrib->crc_err) {

				if (padapter->registrypriv.mp_mode == 0)
					RTW_INFO("%s()-%d: RX Warning! rx CRC ERROR !!\n", __func__, __LINE__);

				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
				break;
			}

			/*if (rtl8821c_rx_ba_ssn_appended(p))*/
			if (rtw_hal_rcr_check(padapter, BIT_APP_BASSN_8821C))
				rx_report_sz = desc_size + RTW_HALMAC_BA_SSN_RPT_SIZE + pattrib->drvinfo_sz;
			else
				rx_report_sz = desc_size + pattrib->drvinfo_sz;

			pkt_offset = rx_report_sz + pattrib->shift_sz + pattrib->pkt_len;

			if ((pattrib->pkt_len == 0) || (pkt_offset > transfer_len)) {
				RTW_INFO("%s()-%d: RX Warning!, pkt_len==0 or pkt_offset(%d)> transfoer_len(%d)\n", __func__, __LINE__, pkt_offset, transfer_len);
				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
				break;
			}

			if ((pattrib->crc_err) || (pattrib->icv_err)) {
#ifdef CONFIG_MP_INCLUDED
				if (padapter->registrypriv.mp_mode == 1) {
					if (check_fwstate(&padapter->mlmepriv, WIFI_MP_STATE)) { /*&&(padapter->mppriv.check_mp_pkt == 0))*/
						if (pattrib->crc_err == 1)
							padapter->mppriv.rx_crcerrpktcount++;
					}
				}
#endif

				RTW_INFO("%s: crc_err=%d icv_err=%d, skip!\n", __func__, pattrib->crc_err, pattrib->icv_err);
				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
			} else {

				if (pattrib->pkt_rpt_type == NORMAL_RX) { /*Normal rx packet*/

#ifdef CONFIG_RX_PACKET_APPEND_FCS
				if (check_fwstate(&padapter->mlmepriv, WIFI_MONITOR_STATE) == _FALSE)
					if ((pattrib->pkt_rpt_type == NORMAL_RX) && rtw_hal_rcr_check(padapter, BIT_APP_FCS_8821C))
						pattrib->pkt_len -= IEEE80211_FCS_LEN;
#endif

				if (rtw_os_alloc_recvframe(padapter, precvframe,
					(ptr + rx_report_sz + pattrib->shift_sz), precvbuf->pskb) == _FAIL) {
					rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
					break;
				}

				recvframe_put(precvframe, pattrib->pkt_len);
				/*recvframe_pull(precvframe, drvinfo_sz + RXDESC_SIZE);*/


				/* update drv info*/
#if 0
				if (rtw_hal_rcr_check(padapter, BIT_APP_BASSN_8821C)) {
					/*rtl8821c_update_bassn(padapter, (ptr + RXDESC_SIZE));*/
				}
#endif

					pre_recv_entry(precvframe, pattrib->physt ? (ptr + rx_report_sz - pattrib->drvinfo_sz) : NULL);

				} else { /* C2H_PACKET */

					c2h_pre_handler_rtl8821c(padapter, ptr, transfer_len);
					rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);

				}
			}

			/* Page size of receive package is 128 bytes alignment =>DMA AGG*/

			pkt_offset = _RND8(pkt_offset);
			transfer_len -= pkt_offset;
			ptr += pkt_offset;
			precvframe = NULL;

		} while (transfer_len > 0);

		precvbuf->len = 0;

		rtw_enqueue_recvbuf(precvbuf, &precvpriv->free_recv_buf_queue);
	} while (1);

#ifdef CONFIG_RTW_NAPI
#ifdef CONFIG_RTW_NAPI_V2
	if (padapter->registrypriv.en_napi) {
		struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
		struct _ADAPTER *iface;
		u8 i;

		for (i = 0; i < dvobj->iface_nums; i++) {
			iface = dvobj->padapters[i];
			precvpriv = &iface->recvpriv; 
			if (rtw_if_up(iface) == _TRUE
				&& skb_queue_len(&precvpriv->rx_napi_skb_queue))
				napi_schedule(&iface->napi);
		}
	}
#endif /* CONFIG_RTW_NAPI_V2 */
#endif /* CONFIG_RTW_NAPI */

	return _SUCCESS;

}

static void rtl8821c_recv_tasklet(void *priv)
{
	_adapter *adapter = (_adapter *)priv;
	s32 ret;

	ret = rtl8821cs_recv_hdl(adapter);
	if (ret == RTW_RFRAME_UNAVAIL
		|| ret == RTW_RFRAME_PKT_UNAVAIL
	) {
		/* schedule again and hope recvframe/packet is available next time. */
		#ifdef PLATFORM_LINUX
		tasklet_schedule(&adapter->recvpriv.recv_tasklet);
		#endif
	}
}

#else

static void rtl8821c_recv_tasklet(void *priv)
{
	PADAPTER				padapter;
	PHAL_DATA_TYPE			pHalData;
	struct recv_priv		*precvpriv;
	struct recv_buf			*precvbuf;
	union recv_frame		*precvframe;
	struct recv_frame_hdr	*phdr;
	struct rx_pkt_attrib	*pattrib;
	u8		*ptr;
	_pkt		*ppkt;
	u32		desc_size;
	u32		pkt_offset;

	padapter = (PADAPTER)priv;
	pHalData = GET_HAL_DATA(padapter);
	precvpriv = &padapter->recvpriv;
	rtw_halmac_get_rx_desc_size(adapter_to_dvobj(padapter), &desc_size);

	do {
		precvbuf = rtw_dequeue_recvbuf(&precvpriv->recv_buf_pending_queue);
		if (NULL == precvbuf)
			break;

		ptr = precvbuf->pdata;

		while (ptr < precvbuf->ptail) {
			precvframe = rtw_alloc_recvframe(&precvpriv->free_recv_queue);
			if (precvframe == NULL) {
				RTW_ERR("%s: no enough recv frame!\n", __FUNCTION__);
				rtw_enqueue_recvbuf_to_head(precvbuf, &precvpriv->recv_buf_pending_queue);

				/* The case of can't allocate recvframe should be temporary,
				schedule again and hope recvframe is available next time.*/
				tasklet_schedule(&precvpriv->recv_tasklet);

				return;
			}

			phdr = &precvframe->u.hdr;
			pattrib = &phdr->attrib;

			/*rx desc parsing*/
			rtl8821c_rxdesc2attribute(pattrib, ptr);

			/* fix Hardware RX data error, drop whole recv_buffer*/
			if (!rtw_hal_rcr_check(padapter, BIT_ACRC32_8821C) && pattrib->crc_err) {
				/*#if !(MP_DRIVER==1)*/
				if (padapter->registrypriv.mp_mode == 0)
					RTW_INFO("%s()-%d: RX Warning! rx CRC ERROR !!\n", __FUNCTION__, __LINE__);
				/*#endif*/
				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
				break;
			}

			pkt_offset = desc_size + pattrib->drvinfo_sz + pattrib->pkt_len;

			if ((ptr + pkt_offset) > precvbuf->ptail) {
				RTW_INFO("%s()-%d: : next pkt len(%p,%d) exceed ptail(%p)!\n", __FUNCTION__, __LINE__, ptr, pkt_offset, precvbuf->ptail);
				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
				break;
			}

			if ((pattrib->crc_err) || (pattrib->icv_err)) {
#ifdef CONFIG_MP_INCLUDED
				if (padapter->registrypriv.mp_mode == 1) {
					if (check_fwstate(&padapter->mlmepriv, WIFI_MP_STATE)) { /*&&(padapter->mppriv.check_mp_pkt == 0))*/
						if (pattrib->crc_err == 1)
							padapter->mppriv.rx_crcerrpktcount++;
					}
				}
#endif

				RTW_INFO("%s: crc_err=%d icv_err=%d, skip!\n", __func__, pattrib->crc_err, pattrib->icv_err);
				rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
			} else {
				ppkt = rtw_skb_clone(precvbuf->pskb);
				if (ppkt == NULL) {
					rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
					rtw_enqueue_recvbuf_to_head(precvbuf, &precvpriv->recv_buf_pending_queue);

					/* The case of can't allocate skb is serious and may never be recovered,
					 once bDriverStopped is enable, this task should be stopped.*/
					if (!rtw_is_drv_stopped(padapter))
						tasklet_schedule(&precvpriv->recv_tasklet);

					return;
				}

				phdr->pkt = ppkt;
				phdr->len = 0;
				phdr->rx_head = precvbuf->phead;
				phdr->rx_data = phdr->rx_tail = precvbuf->pdata;
				phdr->rx_end = precvbuf->pend;

				recvframe_put(precvframe, pkt_offset);
				recvframe_pull(precvframe, desc_size + pattrib->drvinfo_sz);
				skb_pull(ppkt, desc_size + pattrib->drvinfo_sz);

#ifdef CONFIG_RX_PACKET_APPEND_FCS
				if (check_fwstate(&padapter->mlmepriv, WIFI_MONITOR_STATE) == _FALSE) {
					if ((pattrib->pkt_rpt_type == NORMAL_RX) && rtw_hal_rcr_check(padapter, BIT_APP_FCS_8821C)) {
						recvframe_pull_tail(precvframe, IEEE80211_FCS_LEN);
						pattrib->pkt_len -= IEEE80211_FCS_LEN;
						ppkt->len = pattrib->pkt_len;
					}
				}
#endif

				/* move to drv info position*/
				ptr += desc_size;

				/* update drv info*/
				if (rtw_hal_rcr_check(padapter, BIT_APP_BASSN_8821C)) {
					/*rtl8821cs_update_bassn(padapter, pdrvinfo);*/
					ptr += RTW_HALMAC_BA_SSN_RPT_SIZE;
				}

				if (pattrib->pkt_rpt_type == NORMAL_RX) /*Normal rx packet*/
					pre_recv_entry(precvframe, pattrib->physt ? ptr : NULL);
				else { /* C2H_PACKET*/
					c2h_pre_handler_rtl8821c(padapter, ptr, transfer_len);
					rtw_free_recvframe(precvframe, &precvpriv->free_recv_queue);
				}
			}

			/* Page size of receive package is 128 bytes alignment =>DMA AGG*/

			pkt_offset = _RND8(pkt_offset);
			precvbuf->pdata += pkt_offset;
			ptr = precvbuf->pdata;

		}

		rtw_skb_free(precvbuf->pskb);
		precvbuf->pskb = NULL;
		rtw_enqueue_recvbuf(precvbuf, &precvpriv->free_recv_buf_queue);

	} while (1);

}
#endif

/*
 * Initialize recv private variable for hardware dependent
 * 1. recv buf
 * 2. recv tasklet
 */
s32 rtl8821cs_init_recv_priv(PADAPTER adapter)
{
	struct registry_priv *regsty = adapter_to_regsty(adapter);
	s32 res;
	u32 i, n;
	struct recv_priv *precvpriv;
	struct recv_buf *precvbuf;


	res = _SUCCESS;
	precvpriv = &adapter->recvpriv;

	/* 1. init recv buffer */
	_rtw_init_queue(&precvpriv->free_recv_buf_queue);
	_rtw_init_queue(&precvpriv->recv_buf_pending_queue);

	n = regsty->recvbuf_nr * sizeof(struct recv_buf) + 4;
	precvpriv->pallocated_recv_buf = rtw_zmalloc(n);
	if (precvpriv->pallocated_recv_buf == NULL) {
		res = _FAIL;
		goto exit;
	}

	precvpriv->precv_buf = (u8 *)N_BYTE_ALIGMENT((SIZE_PTR)(precvpriv->pallocated_recv_buf), 4);

	/* init each recv buffer */
	precvbuf = (struct recv_buf *)precvpriv->precv_buf;
	for (i = 0; i < regsty->recvbuf_nr; i++) {
		res = sdio_initrecvbuf(precvbuf, adapter);
		if (res == _FAIL)
			break;

		res = rtw_os_recvbuf_resource_alloc(adapter, precvbuf, MAX_RECVBUF_SZ);
		if (res == _FAIL) {
			sdio_freerecvbuf(precvbuf);
			break;
		}

#if 0
		res = os_recvbuf_resource_alloc(adapter, precvbuf);
		if (res == _FAIL) {
			sdio_freerecvbuf(precvbuf);
			break;
		}
#endif
		rtw_list_insert_tail(&precvbuf->list, &precvpriv->free_recv_buf_queue.queue);

		precvbuf++;
	}
	precvpriv->free_recv_buf_queue_cnt = i;

	if (res == _FAIL)
		goto initbuferror;

	/* 2. init tasklet */
#ifdef PLATFORM_LINUX
	tasklet_init(&precvpriv->recv_tasklet,
		     (void(*)(unsigned long))rtl8821c_recv_tasklet,
		     (unsigned long)adapter);
#endif

	goto exit;

initbuferror:
	precvbuf = (struct recv_buf *)precvpriv->precv_buf;
	if (precvbuf) {
		n = precvpriv->free_recv_buf_queue_cnt;
		precvpriv->free_recv_buf_queue_cnt = 0;
		for (i = 0; i < n ; i++) {
			rtw_list_delete(&precvbuf->list);
			rtw_os_recvbuf_resource_free(adapter, precvbuf);
			sdio_freerecvbuf(precvbuf);
			precvbuf++;
		}
		precvpriv->precv_buf = NULL;
	}

	if (precvpriv->pallocated_recv_buf) {
		n = regsty->recvbuf_nr * sizeof(struct recv_buf) + 4;
		rtw_mfree(precvpriv->pallocated_recv_buf, n);
		precvpriv->pallocated_recv_buf = NULL;
	}

exit:
	return res;
}

/*
 * Free recv private variable of hardware dependent
 * 1. recv buf
 * 2. recv tasklet
 */
void rtl8821cs_free_recv_priv(PADAPTER adapter)
{
	struct registry_priv *regsty = &adapter->registrypriv;
	u32 i, n;
	struct recv_priv *precvpriv;
	struct recv_buf *precvbuf;


	precvpriv = &adapter->recvpriv;

	/* 1. kill tasklet */
#ifdef PLATFORM_LINUX
	tasklet_kill(&adapter->recvpriv.recv_tasklet);
#endif

	/* 2. free all recv buffers */
	precvbuf = (struct recv_buf *)precvpriv->precv_buf;
	if (precvbuf) {
		n = regsty->recvbuf_nr;
		precvpriv->free_recv_buf_queue_cnt = 0;
		for (i = 0; i < n ; i++) {
			rtw_list_delete(&precvbuf->list);
			rtw_os_recvbuf_resource_free(adapter, precvbuf);
			sdio_freerecvbuf(precvbuf);
			precvbuf++;
		}
		precvpriv->precv_buf = NULL;
	}

	if (precvpriv->pallocated_recv_buf) {
		n = regsty->recvbuf_nr * sizeof(struct recv_buf) + 4;
		rtw_mfree(precvpriv->pallocated_recv_buf, n);
		precvpriv->pallocated_recv_buf = NULL;
	}
}

void rtl8821cs_rxhandler(PADAPTER adapter, struct recv_buf *recvbuf)
{
	struct recv_priv *recvpriv = &adapter->recvpriv;
	_queue *pending_queue = &recvpriv->recv_buf_pending_queue;


	/*enqueue recvbuf*/
	rtw_enqueue_recvbuf(recvbuf, pending_queue);

	/*schedule tasklet*/
#ifdef CONFIG_RECV_THREAD_MODE
	_rtw_up_sema(&recvpriv->recv_sema);
#else
#ifdef PLATFORM_LINUX
	tasklet_schedule(&recvpriv->recv_tasklet);
#endif
#endif
}
